<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Demo</title>
    <script>
        function DataBinder(object_id) {
            // Create a simple PubSub object
            var pubSub = {
                    callbacks: {},

                    on: function (msg, callback) {
                        this.callbacks[msg] = this.callbacks[msg] || [];
                        this.callbacks[msg].push(callback);
                    },

                    publish: function (msg) {
                        this.callbacks[msg] = this.callbacks[msg] || [];
                        for (var i = 0, len = this.callbacks[msg].length; i < len; i++) {
                            this.callbacks[msg][i].apply(this, arguments);
                        }
                    }
                },

                data_attr = "data-bind-" + object_id,
                message = object_id + ":input",
                timeIn;

            changeHandler = function (evt) {
                var target = evt.target || evt.srcElement, // IE8 compatibility
                    prop_name = target.getAttribute(data_attr);

                if (prop_name && prop_name !== "") {
                    clearTimeout(timeIn);
                    timeIn = setTimeout(function () {
                        pubSub.publish(message, prop_name, target.value);
                    }, 50);

                }
            };

            // Listen to change events and proxy to PubSub
            if (document.addEventListener) {
                document.addEventListener("input", changeHandler, false);
            } else {
                // IE8 uses attachEvent instead of addEventListener
                document.attachEvent("oninput", changeHandler);
            }

            // PubSub propagates changes to all bound elements
            pubSub.on(message, function (evt, prop_name, new_val) {
                var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"),
                    tag_name;

                for (var i = 0, len = elements.length; i < len; i++) {
                    tag_name = elements[i].tagName.toLowerCase();

                    if (tag_name === "input" || tag_name === "textarea" || tag_name === "select") {
                        elements[i].value = new_val;
                    } else {
                        elements[i].innerHTML = new_val;
                    }
                }
            });

            return pubSub;
        }

        function DBind(uid) {
            var binder = new DataBinder(uid),

                user = {
                    // ...
                    attributes: {},
                    set: function (attr_name, val) {
                        this.attributes[attr_name] = val;
                        // Use the `publish` method
                        binder.publish(uid + ":input", attr_name, val, this);
                    },
                    get: function (attr_name) {
                        return this.attributes[attr_name];
                    },

                    _binder: binder
                };

            // Subscribe to the PubSub
            binder.on(uid + ":input", function (evt, attr_name, new_val, initiator) {
                if (initiator !== user) {
                    user.set(attr_name, new_val);
                }
            });

            return user;
        }
    </script>
</head>

<body>
    <input type="text" data-bind-1="name" />
    <span data-bind-1="name"></span>
    <script>
        var DBind = new DBind(1);
        DBind.set("name", "黄奇");
        setTimeout(function () {
            DBind.set("name", "黄老板");
            alert(DBind.get("name"));
        }, 1000);
    </script>
</body>

</html>